#pragma once
#include <iostream>
#include <poll.h>
#include "Socket.hpp"
#include <string>

using namespace socket_ns;

class PollServer
{
    const static int gnum = 1024;
    const static int gdefaultfd = -1;
    const static int gdefaultevents = 0;
    const static int gdefaultrevents = 0;

public:
    PollServer(uint16_t port)
        : _port(port), _listenSock(std::make_unique<TcpSocket>()), _timeout(1000)
    {
        InetAddr addr("0", _port);
        _listenSock->BuildListenSocket(addr);
        // 够初始化一下
        for (int i = 0; i < gnum; i++)
        {
            _fd_array[i].fd = gdefaultfd;
            _fd_array[i].events = gdefaultevents;
            _fd_array[i].revents = gdefaultrevents;
        }
        // 刚开始只有一个rfd，就是listen套接字
        _fd_array[0].fd = _listenSock->SockFd();
        _fd_array[0].events = POLLIN;
        _fd_array[0].revents = 0;
    }
    void AcceptClint()
    {
        InetAddr client_addr;
        int sockfd = _listenSock->Accepter(&client_addr); // 这里修改一下Accepter函数，返回值用原始的文件描述符
        if (sockfd < 0)
        {
            return;
        }
        LOG(INFO, "get new link success sockfd:%d client info %s:%d\n", sockfd, client_addr.Ip().c_str(), client_addr.Port());
        int pos = 0;
        // 找到新增文件描述符插入的位置
        for (; pos < gnum; pos++)
        {
            if (_fd_array[pos].fd == gdefaultfd)
                break;
        }
        if (pos == gnum)
        {
            // 执行扩容等其他做法 ToDo
        }
        else
        {
            _fd_array[pos].fd = sockfd;
            _fd_array[pos].events = POLLIN;
            _fd_array[pos].revents = 0;
            LOG(DEBUG, "%d add to _fd_array\n", sockfd);
        }
        LOG(INFO, "_fd_array list is %s\n", RfdsToStr().c_str()); // 查看一下此时新增一共有多少个有效需要关心的文件描述符
    }
    std::string RfdsToStr()
    {
        std::string rfdstr;
        for (int i = 0; i < gnum; i++)
        {
            if (_fd_array[i].fd == gdefaultfd)
                continue;
            rfdstr += std::to_string(_fd_array[i].fd);
            rfdstr += " ";
        }
        return rfdstr;
    }
    void ServiceIo(int pos)
    {
        char buffer[1024];
        ssize_t n = ::recv(_fd_array[pos].fd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say#" << buffer << std::endl;
            std::string resp = "[Server Echo]# ";
            resp += buffer;
            ::send(_fd_array[pos].fd, resp.c_str(), resp.size(), 0);
        }
        else if (n == 0)
        {
            LOG(DEBUG, "%d is closed\n", _fd_array[pos].fd);
            ::close(_fd_array[pos].fd);
            _fd_array[pos].fd = gdefaultfd;
            _fd_array[pos].events = gdefaultevents;
            _fd_array[pos].revents = gdefaultrevents;
            LOG(INFO, "_fd_array list is %s\n", RfdsToStr().c_str()); // 查看一下此时减少一个后一共有多少个有效需要关心的文件描述符
        }
        else
        {
            LOG(DEBUG, "%d recv error\n", _fd_array[pos].fd);
            ::close(_fd_array[pos].fd);
            _fd_array[pos].fd = gdefaultfd;
            _fd_array[pos].events = gdefaultevents;
            _fd_array[pos].revents = gdefaultrevents;                 // 关闭文件描述符，并将其从我们的辅助数组里面移除掉，这样下次select不会再关心这个无效的文件描述符
            LOG(INFO, "_fd_array list is %s\n", RfdsToStr().c_str()); // 查看一下此时减少一个后一共有多少个有效需要关心的文件描述符
        }
    }
    void Handler()
    {
        // 处理的时候可能是一个或者多个就绪，我们需要一个一个处理
        for (int i = 0; i < gnum; i++)
        {
            if (_fd_array[i].fd == gdefaultfd)
                continue;
            int fd = _fd_array[i].fd;
            short revents = _fd_array[i].revents;
            // 是合法的fd时，判断事件是否就绪
            if (revents & POLLIN)
            {
                // 合法的话，判断是否是listen套接字就绪新增文件描述符，还是normal套接字就绪正常处理消息接收和发送
                if (fd == _listenSock->SockFd())
                {
                    AcceptClint();
                }
                else
                {
                    ServiceIo(i);
                }
            }
        }
    }
    void Loop()
    {
        while (true)
        {
            int n = poll(_fd_array, gnum, _timeout);
            switch (n)
            {
            case 0:
                LOG(INFO, "timeout....\n"); // 刚开始只有一个listen套接字，没人链接会一直超时
                break;
            case -1:
                LOG(ERROR, "poll error\n");
                break;
            default:
            {
                LOG(DEBUG, "Events happen.. n: %d\n", n); // 虽然刚开始只有一个listen套接字，一旦有人连接，就会一直就绪，我们需要处理事件
                Handler();
            }
            break;
            }
        }
    }
    ~PollServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listenSock;
    struct pollfd _fd_array[gnum]; // 开固定大小，至于扩容之后再处理
    int _timeout;
};
